-- 2022-8-10

---[[ 序列化

    -- 我们常常需要将某些数据序列化/串行化 即将数据转换为字节流或字符流 以便将其存储到文件中或者通过网络传输
    -- 我们也可以将序列化后的数据表示为Lua代码 当这些代码运行时 被序列化的数据就可以在读取程序中得到重建

    -- 通常 如果想要恢复一个全局变量的值 那么可能会使用形如varname=exp这样的代码
    -- 其中 exp是用于创建这个值的Lua代码 而varname是一个简单的标识符 接下来 让我们学习如何编写创建值的代码
    -- 例如 对于一个数值类型而言 可以简单的使用如下代码
    function serialize(o)
        if type(o) ==  "number" then
            io.write(tostring(o))
        else
            -- other cases
        end
    end
    -- 不过 用十进制格式保存浮点数可能丢失精度 此时可以用十六进制来避免这个问题 使用格式"%a"可以保留被读取浮点型书的原始精度
    -- 此外 由于从Lua5.3开始就对浮点类型和整数类型进行了区分 因此通过使用正确的子类型就能够恢复它们的值
    local fmt = {integer = "%d",float = "%a"}
    function serialize(o)
        if type(o)=="number" then
            io.write(string.format(fmt[math.type(o)],o))
        else
            -- other cases
        end
    end
    -- 对于字符串类型的值 最简单的序列化方式形如
    if type(o) == "string" then
        io.write("'",o,"'")
    end
    -- 不过 如果字符串包含特殊字符(比如引号或换行符) 那么结果就会是错误的
    -- 也许有人会告诉读者通过修改引号来解决这个问题
    if type(o) == "string" then
        io.write("[[",o,"]]")
    end
    -- 这里要当选代码注入(code injection) 如果某个恶意用户设法使读者的程序保存了形如"]]..os.execute('rm *')..[["这样的内容
    -- (例如 恶意用户可以将其住址保存为改字符串)，那么接下来的代码将变成:
    local varname = [[]]..os.execute('rm *')..[[]]
    -- 一旦这样的数据被加载 就会导致意想不到的后果

    -- 我们可以使用一种安全的方法来括住一个字符串 那就是使用函数string,format的"%q"选项
    -- 该选项被设计为以一种能够让Lua语言安全的反序列化字符串的方式来序列化字符串
    -- 它使用双括号括住字符串并正确的转义其中的双引号和换行符等其他字符
    local a = 'a "problematic" \\string'
    print(string.format("%q",a))
    
    -- 通过使用这个特性 函数serialize将变为
    function serialize(o)
        if type(o) == "number" then
            io.write(string.format(fmt[math.type(o)],o))
        elseif type(o) == "string" then
            io.write(string.format("%q",o))
        else
            -- other cases
        end
    end

    -- Lua5.3.3对格式选项"%q"进行了扩展 使其也可以用于数值 nil和Boolean类型 进而使它们能够正确的被序列化和反序列化
    -- (特别的 这个格式选项以十六进制格式处理浮点类型以保留完整的精度)
    -- 因此从Lua5.3.3开始 我们还能够再对函数serialize进行进一步的简化和扩展
    function serialize(o)
        local t = type(o)
        if t == "number" or t == "string" or t == "Boolean" or t == "nil" then
            io.write(string.format("%q",o))
        else
            -- other cases
        end
    end

    -- 另一种保存字符串的方式是使用主要用于长字符串的[=[...]=]
    -- 不过 这种方式主要是为不用改变字符串常量的手写代码提供的
    -- 在自动生成的代码中 像函数string.format那样使用"%q"选项来转义有问题的字符更加简单

    -- 尽管如此 如果要在自动生成的代码中使用[=[...]=] 那么还必须要注意几个细节
    -- 我们必须选择恰当数量的等号 这个恰当的数量应比原字符串中出现的最长等号序列的长度大1
    -- 由于在字符串中出现长等号序列很常见(例如代码中的注释) 因此我们应该把注意力集中在方括号开头的等号序列上
    -- 其次 Lua语言总是会忽略长字符串开头的换行符 要解决这个问题可以通过一种简单的方式 即总是在字符串开头多增加一个换行符(这个换行符会被忽略)

    -- 如下的示例函数quote考虑了上述注意事项
    -- 引用任意字符常量
    function quote(s)
        -- 寻找最长等号序列的长度
        local n = -1
        for w in string.gmatch(s,"]=*") do
            n = math.max(n,#w-1)    -- -1用于移除']'
        end
        -- 生成一个具有'n'+1个等号的字符串序列
        local eq = string.rep("=",n+1)
        -- 创建被引起来的字符串
        return string.format(" [%s[\n%s]%s] ",eq,s,eq)
    end
    -- 该函数可以接收任意一个字符串 并返回按字符串对其进行格式化后的结果
    -- 函数gmatch创建一个遍历字符串s中所有匹配模式']=*'之处的迭代器(即右方括号后跟零个或多个等号)
    -- 在每个匹配的地方 循环会用当前所遇到的最大等号数量更新变量n
    -- 循环结束后 使用函数string.rep重复等号n+1次 也就是生成一个比原字符串中出现的最长等号序列的长度大1的等号序列
    -- 最后 使用函数string.format将s放入一对具有正确数量等号的方括号中 并在字符串s的开头插入一个换行符
--]]